/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef __LINUX_ROUTING_LINK_INTERNAL_HPP__
#define __LINUX_ROUTING_LINK_INTERNAL_HPP__

#include <errno.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/socket.h>

#include <linux/if.h> // Must be included after sys/socket.h.

#include <netlink/cache.h>
#include <netlink/errno.h>
#include <netlink/socket.h>

#include <netlink/route/link.h>

#include <string>

#include <stout/error.hpp>
#include <stout/none.hpp>
#include <stout/os.hpp>
#include <stout/result.hpp>
#include <stout/try.hpp>

#include "linux/routing/internal.hpp"

namespace routing {
namespace link {
namespace internal {

// Returns the netlink link object associated with a given link by its
// name. Returns None if the link is not found.
inline Result<Netlink<struct rtnl_link> > get(const std::string& link)
{
  Try<Netlink<struct nl_sock> > sock = routing::socket();
  if (sock.isError()) {
    return Error(sock.error());
  }

  // Dump all the netlink link objects from kernel. Note that the flag
  // AF_UNSPEC means all available families.
  struct nl_cache* c = NULL;
  int err = rtnl_link_alloc_cache(sock.get().get(), AF_UNSPEC, &c);
  if (err != 0) {
    return Error(nl_geterror(err));
  }

  Netlink<struct nl_cache> cache(c);
  struct rtnl_link* l = rtnl_link_get_by_name(cache.get(), link.c_str());
  if (l == NULL) {
    return None();
  }

  return Netlink<struct rtnl_link>(l);
}


// Returns the netlink link object associated with a given link by its
// interface index. Returns None if the link is not found.
inline Result<Netlink<struct rtnl_link> > get(int index)
{
  Try<Netlink<struct nl_sock> > sock = routing::socket();
  if (sock.isError()) {
    return Error(sock.error());
  }

  // Dump all the netlink link objects from kernel. Note that the flag
  // AF_UNSPEC means all available families.
  struct nl_cache* c = NULL;
  int err = rtnl_link_alloc_cache(sock.get().get(), AF_UNSPEC, &c);
  if (err != 0) {
    return Error(nl_geterror(err));
  }

  Netlink<struct nl_cache> cache(c);
  struct rtnl_link* l = rtnl_link_get(cache.get(), index);
  if (l == NULL) {
    return None();
  }

  return Netlink<struct rtnl_link>(l);
}


// Tests if the flags are set on the link. Returns None if the link is
// not found.
inline Result<bool> test(const std::string& _link, unsigned int flags)
{
  Result<Netlink<struct rtnl_link> > link = get(_link);
  if (link.isError()) {
    return Error(link.error());
  } else if (link.isNone()) {
    return None();
  }

  return flags == (rtnl_link_get_flags(link.get().get()) & flags);
}


// Sets the flags on the link. Returns false if the link is not found.
inline Try<bool> set(const std::string& _link, unsigned int flags)
{
  Result<Netlink<struct rtnl_link> > link = get(_link);
  if (link.isError()) {
    return Error(link.error());
  } else if (link.isNone()) {
    return false;
  }

  // TODO(jieyu): We use ioctl to set the flags because the interfaces
  // in libnl have some issues with virtual devices.
  struct ifreq ifr;
  memset(&ifr, 0, sizeof(ifr));

  // Get the existing flags and take a bit-wise OR.
  ifr.ifr_flags = (rtnl_link_get_flags(link.get().get()) | flags);

  strncpy(ifr.ifr_name, _link.c_str(), IFNAMSIZ);

  int fd = ::socket(AF_INET, SOCK_STREAM, 0);
  if (fd == -1) {
    return ErrnoError();
  }

  if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) {
    if (errno == ENODEV) {
      os::close(fd);
      return false;
    } else {
      // Save the error string as os::close may overwrite errno.
      std::string message = strerror(errno);
      os::close(fd);
      return Error(message);
    }
  }

  return true;
}

} // namespace internal {
} // namespace link {
} // namespace routing {

#endif // __LINUX_ROUTING_LINK_INTERNAL_HPP__
